package src;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;

public class AlmightyAlgorithm {
		
	private Context[][] globalContext = null;
	private Context[] objectContexts = null;
	private Context[] attributeContexts = null;
	private Integer logicDegree = 0;
	private Logic logic = null;
	private Integer globalContextObjectsCount = 0;
	private Integer globalContextAttributesCount = 0;
	private Context[][] directProducts = null;
	private List<List<Double>> bonds = null;
	private Map<Context, List<Concept>> mapO = new HashMap<Context, List<Concept>>();
	private Map<Context, List<Concept>> mapA = new HashMap<Context, List<Concept>>();
	
	public AlmightyAlgorithm(File globalContextDataFile, File inputContextsDataFile, Integer logicDegree) {
	
//		find out logic degree from data
//		Integer logicDegreeFromGlobalContext = getLogicDegreeFromData(globalContextDataFile);
//		Integer logicDegreeFromInputContexts = getLogicDegreeFromData(inputContextsDataFile);	
//		logicDegreeFromData = Math.max(logicDegreeFromGlobalContext, logicDegreeFromInputContexts);
		
		this.logicDegree = logicDegree;
		
		getGlobalContextData(globalContextDataFile);
		
		getInputContextsData(inputContextsDataFile);
		
		globalContextObjectsCount = globalContext.length;
		globalContextAttributesCount = globalContext[0].length;
	}
	
	private Integer getLogicDegreeFromData(File dataFile) {
		
//		find out logic degree from data		
		Set<Double> distinctValues = new HashSet<Double>();
		Scanner sc = null;				
		
		try {
			sc = new Scanner(dataFile);
			
			while (sc.hasNextLine()) {
				String line = sc.nextLine();
				if (!line.toLowerCase().startsWith("context")) {
					String[] values = line.split(" ");
					for (String value : values) {
						distinctValues.add(Double.parseDouble(value));
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sc.close();
		}		
		
		return distinctValues.size();
	}
	
	private void getGlobalContextLengths(File dataFile) {
		Scanner sc = null;
		
		int k = 0;
		int l = 0;
		
		try {
			sc = new Scanner(dataFile);
			
			while (sc.hasNextLine()) {
				String line = sc.nextLine();
				
				if (line.toLowerCase().startsWith("context")) {
					String[] values = line.split(" ");
					for (int i = 0; i < values.length; i++) {
						if (values[i].toLowerCase().equals("o")) {
							k = Integer.parseInt(values[i + 1]);
						}
						if (values[i].toLowerCase().equals("a")) {
							l = Integer.parseInt(values[i + 1]);
						}
					}
				}
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sc.close();
		}
		
		globalContext = new Context[k][l];
	}
	
	private void getGlobalContextData(File dataFile) {
		
		getGlobalContextLengths(dataFile);
		
		Scanner sc = null;
		
		try {
			sc = new Scanner(dataFile);			
			int j = 0;
			int k = 0;
			int l = 0;
			List<List<Double>> relation = new ArrayList<List<Double>>();

			do {
				String line = sc.nextLine();
								
				if (line.toLowerCase().startsWith("context")) {										
//					get indices
					relation = new ArrayList<List<Double>>();
					j = 0;
					String[] values = line.split(" ");
					for (int i = 0; i < values.length; i++) {
						if (values[i].toLowerCase().equals("o")) {
							k = Integer.parseInt(values[i + 1]) - 1;
						}
						if (values[i].toLowerCase().equals("a")) {
							l = Integer.parseInt(values[i + 1]) - 1;
						}						
					}						
				} else {				
//					build the relation					
					String[] values = line.split(" ");
					relation.add(new ArrayList<Double>());
					
					for (int i = 0; i < values.length; i++) {
						relation.get(j).add(Double.parseDouble(values[i]));
					}
					j++;
				}
				
				if (!relation.isEmpty()) {
					globalContext[k][l] = new Context(logicDegree, relation);
				}
			} while (sc.hasNextLine());
						
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sc.close();
		}
	}
	
	private void getInputContextsLengths(File dataFile) {
		Scanner sc = null;
		
		int k = 0;
		int l = 0;
		
		try {
			sc = new Scanner(dataFile);
			
			while (sc.hasNextLine()) {
				String line = sc.nextLine();
				
				if (line.toLowerCase().startsWith("context")) {
					String[] values = line.split(" ");
					for (int i = 0; i < values.length; i++) {
						if (values[i].toLowerCase().equals("o")) {
							k = Integer.parseInt(values[i + 1]);
						}
						if (values[i].toLowerCase().equals("a")) {
							l = Integer.parseInt(values[i + 1]);
						}
					}
				}
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sc.close();
		}
		
		objectContexts = new Context[k];
		attributeContexts = new Context[l];
	}
	
	private void getInputContextsData(File dataFile) {
		
		getInputContextsLengths(dataFile);
		
		Scanner sc = null;
		
		try {
			sc = new Scanner(dataFile);			
			int j = 0;
			int k = 0;
			int l = 0;
			boolean objectRelationWasBuilt = false;
			boolean attributeRelationWasBuilt = false;			
			List<List<Double>> relation = new ArrayList<List<Double>>();
			
			do {
				String line = sc.nextLine();
				
				if (line.toLowerCase().startsWith("context")) {					
					relation = new ArrayList<List<Double>>();
					j = 0;
					
//					get indices				
					String[] values = line.split(" ");
					for (int i = 0; i < values.length; i++) {
						if (values[i].toLowerCase().equals("o")) {
							k = Integer.parseInt(values[i + 1]) - 1;
							objectRelationWasBuilt = true;
							attributeRelationWasBuilt = false;
						}
						if (values[i].toLowerCase().equals("a")) {
							l = Integer.parseInt(values[i + 1]) - 1;
							objectRelationWasBuilt = false;
							attributeRelationWasBuilt = true;
						}						
					}						
				} else {				
//					build the relation					
					String[] values = line.split(" ");
					relation.add(new ArrayList<Double>());
					
					for (int i = 0; i < values.length; i++) {
						relation.get(j).add(Double.parseDouble(values[i]));
					}
					j++;
				}								
				
				if (!relation.isEmpty()) {
//					add new context to the list	
					if (objectRelationWasBuilt) {
						objectContexts[k] = new Context(logicDegree, relation);	
					} else if (attributeRelationWasBuilt) {
						attributeContexts[l] = new Context(logicDegree, relation);	
					}					
				}				
			} while (sc.hasNextLine());
						
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sc.close();
		}
	}
	
	private List<List<Double>> toList(Double[][] array) {
		
		List<List<Double>> list = new ArrayList<List<Double>>();
		
		for (int i = 0; i < array.length; i++) {
			list.add(new ArrayList<Double>());
			for (int j = 0; j < array[0].length; j++) {
				list.get(i).add(array[i][j]);
			}
		}
		
		return list;
	}
	
	private List<List<Double>> vectorToRelation(List<Double> vector, Integer objectsCount, Integer attributesCount) {
		List<List<Double>> relation = new ArrayList<List<Double>>();
		
		if (objectsCount * attributesCount != vector.size()) {
			System.err.println("Objects count = " + objectsCount + " * attributes count = " + attributesCount + " is not equal to the size of vector " + vector.size());
		}
		
		int p = 0;
		for (int i = 0; i < objectsCount; i++) {
			relation.add(new ArrayList<Double>());
			for (int j = 0; j < attributesCount; j++) {
				relation.get(i).add(vector.get(p++));
			}
		}
		
		return relation;
	}
	
	private List<Concept> specialDown(List<Concept> concepts) {
		List<Concept> result = new ArrayList<Concept>();
		
		for (int i = 0; i < globalContextObjectsCount; i++) {	
			Context contextO = objectContexts[i];
			
			List<Double> infimum = new ArrayList<Double>();
			for (int j = 0; j < contextO.getAttributesCount(); j++) {
				infimum.add(1.0);
			}			
			
			for (int j = 0; j < globalContextAttributesCount; j++) {				
//				do direct product
				Context contextA = attributeContexts[j];
				Context directProduct = directProduct(contextO, contextA);
				
				List<Double> vector = getVectorOfObjects(this.globalContext[i][j].getRelation());
				int objectsCount = this.globalContext[i][j].getRelation().length;
				int attributesCount = this.globalContext[i][j].getRelation()[0].length;
//				do closure
				List<Double> bondVector = closureOfVectorOfObjects(vector, directProduct, ClosureType.UP_DOWN);
//				vector -> relation
				List<List<Double>> bond = vectorToRelation(bondVector, objectsCount, attributesCount);
				
				List<Double> prechodR = prechodR(bond, concepts.get(j), contextO, contextA);
				infimum = infimum(infimum, prechodR);
			}
			
			result.add(new Concept(contextO.down(infimum), infimum));
		}
		
		return result;
	}
	
	private List<Concept> specialUp(List<Concept> concepts) {
		List<Concept> result = new ArrayList<Concept>();
		
		for (int i = 0; i < globalContextAttributesCount; i++) {			
			Context contextA = attributeContexts[i];
			List<Double> infimum = new ArrayList<Double>();
			for (int j = 0; j < contextA.getObjectsCount(); j++) {
				infimum.add(1.0);
			}			
			
			for (int j = 0; j < globalContextObjectsCount; j++) {
//				do direct product
				Context contextO = objectContexts[j];
				Context directProduct = directProduct(contextO, contextA);
				
				List<Double> vector = getVectorOfObjects(this.globalContext[j][i].getRelation());
				int objectsCount = this.globalContext[j][i].getRelation().length;
				int attributesCount = this.globalContext[j][i].getRelation()[0].length;
//				do closure
				List<Double> bondVector = closureOfVectorOfObjects(vector, directProduct, ClosureType.UP_DOWN);
//				vector -> relation
				List<List<Double>> bond = vectorToRelation(bondVector, objectsCount, attributesCount);
				
				List<Double> prechodL = prechodL(bond, concepts.get(j), contextO, contextA);
				infimum = infimum(infimum, prechodL);
			}
			
			result.add(new Concept(infimum, contextA.up(infimum)));
		}
		
		return result;
	}
	
	private List<Double> infimum(List<Double> fuzzySet1, List<Double> fuzzySet2) {
		List<Double> result = new ArrayList<Double>();
		for (int i = 0; i < fuzzySet1.size(); i++) {
			result.add(Math.min(fuzzySet1.get(i), fuzzySet2.get(i)));
		}		
		return result;
	}
	
	private List<Double> prechodR(List<List<Double>> bond, Concept concept, Context contextO, Context contextA) {
		List<Double> fuzzySetOfAttributesB = new ArrayList<Double>();
		List<Double> intent = concept.getIntent();
		List<List<Double>> rightCorrespondence = rightCorrespondence(bond, contextO, contextA);
		
		for (int i = 0; i < contextO.getAttributesCount(); i++) {
			double pom = 0.0;
			
			for (int j = 0; j < contextA.getAttributesCount(); j++) {
				Double conjunction = logic.strongConjunction(rightCorrespondence.get(j).get(i), intent.get(j));
				pom = Math.max(pom, conjunction);				
			}
			fuzzySetOfAttributesB.add(pom);
		}
		return contextO.closure(fuzzySetOfAttributesB, ClosureType.DOWN_UP);
	}	//vrati intent
	
	private List<Double> prechodL(List<List<Double>> bond, Concept concept, Context contextO, Context contextA) {
		List<Double> fuzzySetOfObjectsA = new ArrayList<Double>();
		List<Double> extent = concept.getExtent();
		List<List<Double>> leftCorrespondence = leftCorrespondence(bond, contextO, contextA);
		
		for (int i = 0; i < contextA.getObjectsCount(); i++) {
			double pom = 0.0;
			
			for (int j = 0; j < contextO.getObjectsCount(); j++) {
				Double conjunction = logic.strongConjunction(leftCorrespondence.get(j).get(i), extent.get(j));
				pom = Math.max(pom, conjunction);				
			}
			fuzzySetOfObjectsA.add(pom);
		}
		return contextA.closure(fuzzySetOfObjectsA, ClosureType.UP_DOWN);
	}	//vrati extent
	
	private List<List<Double>> leftCorrespondence(List<List<Double>> bond, Context contextO, Context contextA) {
		List<List<Double>> result = new ArrayList<List<Double>>();
		
		for (int i = 0; i < contextO.getObjectsCount(); i++) {
			result.add(contextA.down(bond.get(i)));
		}
		
		return result;
	}
	
	private List<List<Double>> rightCorrespondence(List<List<Double>> bond, Context contextO, Context contextA) {
		List<List<Double>> result = new ArrayList<List<Double>>();
		
		List<List<Double>> trBond = transpose(bond);
		for (int i = 0; i < contextA.getAttributesCount(); i++) {
			result.add(contextO.up(trBond.get(i)));
		}
		
		return result;
	}
	
    private List<List<Double>> transpose(List<List<Double>> fuzzySets) {
    	List<List<Double>> result = new ArrayList<List<Double>>();
    	
    	for (int i = 0; i < fuzzySets.get(0).size(); i++) {
    		List<Double> temp = new ArrayList<Double>();
    		for (int j = 0; j < fuzzySets.size(); j++) {
    			temp.add(fuzzySets.get(j).get(i));
			}
    		result.add(temp);
		}

    	return result;
    }
	    
	private Context directProduct(Context context1, Context context2) {
		Context directProduct = null;
		Integer logicDegree1 = context1.getLogic().getLogicDegree();
		Integer logicDegree2 = context2.getLogic().getLogicDegree();
		
		if (logicDegree1 != logicDegree2) {
			System.err.println("It is not possible to do direct product of contexts with different logic degree");
		}
		
		logic = new Logic(logicDegree1);
		
		Double[][] relation1 = context1.getRelation();
		Double[][] relation2 = context2.getRelation();
		
		Double[][] directProductRelation = new Double[relation1.length * relation2[0].length][relation1[0].length * relation2.length];
		
		int p = 0;
		int q = 0;		
		for (int i = 0; i < relation1.length; i++) {
			for (int j = 0; j < relation2[0].length; j++) {
				for (int k = 0; k < relation2.length; k++) {
					for (int l = 0; l < relation1[0].length; l++) {						
						directProductRelation[p][q] = logic.disjunction(relation1[i][l], relation2[k][j]);
						q++;
					}
				}
				p++;
				q = 0;				
			}
		}
		
		directProduct = new Context(logicDegree1, directProductRelation);
		
		return directProduct;
	}
    
	private List<Double> closureOfVectorOfObjects(List<Double> vectorOfObjects, Context context, ClosureType closureType) {
		List<Double> closureOfVectorOfObjects = context.closure(vectorOfObjects, closureType);
		return closureOfVectorOfObjects;
	}
	
	private List<Double> getVectorOfObjects(Double[][] relation) {
		Double[] vectorOfObjects = new Double[relation.length * relation[0].length];
		int p = 0;
		for (int i = 0; i < relation.length; i++) {
			for (int j = 0; j < relation[0].length; j++) {
				vectorOfObjects[p] = relation[i][j];
				p++;
			}
		}
		
		return Arrays.asList(vectorOfObjects);
	}
	
	private void execute() {

		Set<?>[] conceptsO = new Set<?>[objectContexts.length];
		Set<?>[] conceptsA = new Set<?>[attributeContexts.length];
		
//		object contexts
		
		System.out.println("object contexts and their concepts:");
		
		for (int i = 0; i < objectContexts.length; i++) {
			Context contextO = objectContexts[i];
			
			contextO.lattice(ClosureType.UP_DOWN);
			
			Set<Concept> concepts = contextO.getConcepts();
			
			conceptsO[i] = contextO.getConcepts();
			
			mapO.put(contextO, new ArrayList<Concept>());
			
			for (Concept concept : concepts) {
				mapO.get(contextO).add(concept);
			}
		}
		
		
		int j = 0;		
		for (Entry<Context, List<Concept>> entry : mapO.entrySet()) {
			System.out.println(++j + ".: ");
			System.out.println(entry.getKey());
			for (int i = 1; i <= entry.getValue().size(); i++) {
				System.out.println(i + ". " + entry.getValue().get(i - 1));			
			}
			System.out.println("----------------------------");				
		}
		System.out.println();
		
		Set<Set<Object>> tuplesO = cartesianProduct(conceptsO);
		System.out.println("number of tuplesO " + tuplesO.size());
		for (Set<Object> tuple : tuplesO) {
			System.out.println("tuple of object concepts");
			for (Object concept : tuple) {
				System.out.println(concept);
			}
			
			List<Concept> tupleOfObjectConcepts = new ArrayList<Concept>();
			for (Object concept : tuple) {
				tupleOfObjectConcepts.add((Concept) concept);
			}
			
			System.out.println("special up");
			List<Concept> tupleOfAttributeConcepts = specialUp(tupleOfObjectConcepts);
			for (Object concept : tupleOfAttributeConcepts) {
				System.out.println(concept);
			}			
			System.out.println("---------------------");
		}
		System.out.println();
		
		
//		attribute contexts
		System.out.println("attribute contexts and their concepts:");
		for (int i = 0; i < attributeContexts.length; i++) {
			Context contextA = attributeContexts[i];
			
			contextA.lattice(ClosureType.DOWN_UP);
			
			Set<Concept> concepts = contextA.getConcepts();
			
			conceptsA[i] = concepts;
			
			mapA.put(contextA, new ArrayList<Concept>());
			
			for (Concept concept : concepts) {
				mapA.get(contextA).add(concept);
			}
		}	
		
		j = 0;
		for (Entry<Context, List<Concept>> entry : mapA.entrySet()) {
			System.out.println(++j + ".: ");
			System.out.println(entry.getKey());
			for (int i = 1; i <= entry.getValue().size(); i++) {
				System.out.println(i + ". " + entry.getValue().get(i - 1));				
			}
			System.out.println("----------------------------");			
		}
		
		Set<Set<Object>> tuplesA = cartesianProduct(conceptsA);
		System.out.println("number of tuplesA " + tuplesA.size());
		for (Set<Object> tuple : tuplesA) {
			System.out.println("tuple of attribute concepts");
			for (Object concept : tuple) {
				System.out.println(concept);
			}
			
			List<Concept> tupleOfAttributeConcepts = new ArrayList<Concept>();
			for (Object concept : tuple) {
				tupleOfAttributeConcepts.add((Concept) concept);
			}
			
			System.out.println("special down");
			List<Concept> tupleOfObjectConcepts = specialDown(tupleOfAttributeConcepts);
			for (Object concept : tupleOfObjectConcepts) {
				System.out.println(concept);
			}			
			System.out.println("---------------------");
		}
	}
	
	/*
	 *	cartesian product for an arbitrary number of sets 
	 */	
	public Set<Set<Object>> cartesianProduct(Set<?>... sets) {
	    if (sets.length < 2)
	        throw new IllegalArgumentException(
	                "Can't have a product of fewer than two sets (got " +
	                sets.length + ")");

	    return _cartesianProduct(0, sets);
	}

	private Set<Set<Object>> _cartesianProduct(int index, Set<?>... sets) {
	    Set<Set<Object>> ret = new HashSet<Set<Object>>();
	    if (index == sets.length) {
	        ret.add(new HashSet<Object>());
	    } else {
	        for (Object obj : sets[index]) {
	            for (Set<Object> set : _cartesianProduct(index+1, sets)) {
	                set.add(obj);
	                ret.add(set);
	            }
	        }
	    }
	    return ret;
	}
	
	public Context[][] getGlobalContext() {
		return globalContext;
	}
	
	public Context[] getObjectContexts() {
		return objectContexts;
	}
	
	public Context[] getAttributeContexts() {
		return attributeContexts;
	}
	
	public Context[][] getDirectProducts() {
		return directProducts;
	}
	
	public List<List<Double>> getBonds() {
		return bonds;
	}
	
	public static void main(String[] args) {
		AlmightyAlgorithm a = new AlmightyAlgorithm(new File("globalContext.dat"), new File("inputContexts.dat"), 3);
//		Context[][] context = a.getGlobalContext();
//		Context[] objectContexts = a.getObjectContexts();
//		Context[] attributeContexts = a.getAttributeContexts();			
//		
////		print all contexts from global context
//		System.out.println("global context:");
//		for (int i = 0; i < context.length; i++) {
//			for (int j = 0; j < context[0].length; j++) {
//				System.out.println(context[i][j]);
//			}
//		}
//		
////		print all object contexts
//		System.out.println("object contexts:");	
//		for (int i = 0; i < objectContexts.length; i++) {
//			System.out.println(objectContexts[i]);
//		}
//		
////		print all attribute contexts
//		System.out.println("attribute contexts:");		
//		for (int i = 0; i < attributeContexts.length; i++) {
//			System.out.println(attributeContexts[i]);
//		}
		
		a.execute();
	}	
}